package com.phychips.rcp;

/**
 * RcpPacket defines a packet and the functions to encode/decode/check the
 * packet.
 */
public class RcpPacket {
    /**
     * packet. byte array
     */
    protected byte[] packet;

    /**
     * packet length
     */
    protected int packet_len;
    /**
     * payload length
     */
    protected int payload_len;
    /**
     * maximum payload length
     */
    private int max_payload_len;
    public static final int PAYLOAD_INDEX = 5;
    private static final int PACKET_LEN_EXCLUDING_PAYLOAD = 8;

    public static final RcpPacket INSTANCE = new RcpPacket();

    private RcpPacket() {
        this.max_payload_len = 256;
        this.packet = new byte[max_payload_len + PACKET_LEN_EXCLUDING_PAYLOAD];
        return;
    }

    public static RcpPacket getInstance() {
        return INSTANCE;
    }

    private byte getPreamble() {
        return packet[0];
    }

    private byte getType() {
        return packet[1];
    }

    private byte getCode() {
        return packet[2];
    }

    private byte getEndMask() {
        return packet[PAYLOAD_INDEX + payload_len];
    }

    private int getUCRC16() throws RcpException {
        return RcpLib.convertByteArrayToInt(packet, PAYLOAD_INDEX + payload_len
                + 1, 2);
    }

    /**
     * This checks the required packet size. It exceeds the current maximum, the
     * new packet is allocated.
     *
     * @param payload_len
     */
    public void checkPacketSize(int payload_len, boolean crc) {
        if (payload_len > this.max_payload_len) {
            this.max_payload_len = payload_len;
            this.packet = new byte[max_payload_len
                    + PACKET_LEN_EXCLUDING_PAYLOAD];
        }
        this.payload_len = payload_len;

        if (crc) {
            this.packet_len = PAYLOAD_INDEX + payload_len + 3;
        } else {
            this.packet_len = PAYLOAD_INDEX + payload_len + 1;
        }

        return;
    }

    /**
     * This starts encoding packet.
     *
     * @param type
     * @param code
     * @param payload_len
     * @throws RcpException
     */
    public void startEncodePacket(byte type, byte code, int payload_len, boolean crc)
            throws RcpException {
        checkPacketSize(payload_len, crc);

        packet[0] = RcpConst.PREAMBLE;
        packet[1] = type;
        packet[2] = code;
        RcpLib.convertIntToByteArray(payload_len, packet, 3, 2);
        packet[PAYLOAD_INDEX + payload_len] = RcpConst.ENDMARK;
    }

    /**
     * This adds the CRC value to the packet.
     *
     * @throws RcpException
     */
    public void endEncodePacket() throws RcpException {
        RcpLib.convertIntToByteArray(RcpLib.calculateCRC(this), packet,
                PAYLOAD_INDEX + payload_len + 1, 2);
    }

    private void checkValidPayloadFormat0(int payload_len) throws RcpException {
        RcpException exception;

        if (this.payload_len != payload_len) {
            exception = new RcpException("invalid packet format",
                    RcpConst.INVALID_PACKET);
            throw exception;
        }
        return;
    }

    private void checkValidPayloadFormat1() throws RcpException {
        RcpException exception;

        if (this.payload_len != 0x01 || this.packet[PAYLOAD_INDEX] != 0x00) {
            exception = new RcpException("invalid packet format",
                    RcpConst.INVALID_PACKET);
            throw exception;
        }
        return;
    }

    private void checkValidPayloadFormat2() throws RcpException {
        RcpException exception;

        if ((RcpLib.covertUShorttoInt(this.packet[PAYLOAD_INDEX + 6]) + 7 != this.payload_len)
                || ((this.packet[PAYLOAD_INDEX + 6] & 0x7F) != 0)) {
            exception = new RcpException("invalid packet format",
                    RcpConst.INVALID_PACKET);
            throw exception;
        }
        return;
    }

    private void checkValidPayloadFormat3() throws RcpException {
        RcpException exception;

        if (this.payload_len != 0x02
                || ((this.packet[PAYLOAD_INDEX + 1] & 0x07) != 0)) {
            exception = new RcpException("invalid packet format",
                    RcpConst.INVALID_PACKET);
            throw exception;
        }
        return;
    }

    private void checkValidPayloadFormat4() throws RcpException {
        RcpException exception;

        if (this.payload_len != (RcpLib
                .covertUbytetoInt(this.packet[PAYLOAD_INDEX]) + 1)) {
            exception = new RcpException("invalid packet format",
                    RcpConst.INVALID_PACKET);
            throw exception;
        }
        return;
    }

    private void checkValidPayloadFormat5() throws RcpException {
        RcpException exception;

        if (this.payload_len != 0x01 || this.packet[PAYLOAD_INDEX] != 0x1F) {
            exception = new RcpException("invalid packet format",
                    RcpConst.INVALID_PACKET);
            throw exception;
        }
        return;
    }

    /**
     * This checks the validness of the packet format.
     *
     * @throws RcpException
     */
    public void checkValidPacket() throws RcpException {
        RcpException exception;

        // general valid check
        if (getPreamble() != RcpConst.PREAMBLE
                || (getType() != RcpConst.RCP_MSG_CMD
                && getType() != RcpConst.RCP_MSG_RSP && getType() != RcpConst.RCP_MSG_NOTI)
                || getEndMask() != RcpConst.ENDMARK) {
            exception = new RcpException("invalid packet format",
                    RcpConst.INVALID_PACKET);
            throw exception;
        }

        if (getUCRC16() != RcpLib.calculateCRC(this)) {
            exception = new RcpException("invalid packet: CRC mismatch",
                    RcpConst.INVALID_PACKET_CRC);
            throw exception;
        }

        if (getType() == RcpConst.RCP_MSG_CMD) {
            return;
        }

        // from here, check the validness of the response or the notification
        // packet
        try {
            if (getCode() == RcpConst.RCP_CMD_CTL_RD_PWR) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_GET_RD_INFO) {
                ; // no check
            } else if (getCode() == RcpConst.RCP_CMD_GET_REGION) {
                checkValidPayloadFormat0(0x01); // format0
            } else if (getCode() == RcpConst.RCP_CMD_SET_REGION) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_CTL_RESET) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_GET_C_SEL_PARAM) {
                checkValidPayloadFormat2(); // format2
            } else if (getCode() == RcpConst.RCP_CMD_SET_C_SEL_PARAM) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_GET_C_QRY_PARAM) {
                checkValidPayloadFormat3(); // format3
            } else if (getCode() == RcpConst.RCP_CMD_SET_C_QRY_PARAM) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_GET_CH) {
                checkValidPayloadFormat0(0x02); // format0
            } else if (getCode() == RcpConst.RCP_CMD_SET_CH) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_GET_FH_LBT) {
                checkValidPayloadFormat0(0x0B); // format0
            } else if (getCode() == RcpConst.RCP_CMD_SET_FH_LBT) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_GET_TX_PWR) {
                checkValidPayloadFormat0(0x02); // format0
            } else if (getCode() == RcpConst.RCP_CMD_SET_TX_PWR) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_CTL_CW) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_READ_C_UII) {
                // checkValidPayloadFormat0(0x0E); // no check
            } else if (getCode() == RcpConst.RCP_CMD_READ_C_USER_DT) {
                // checkValidPayloadFormat0(0x0E); // no check
            } else if (getCode() == RcpConst.RCP_CMD_STRT_AUTO_READ) {
                if (getType() == RcpConst.RCP_MSG_RSP) {
                    checkValidPayloadFormat1(); // format1
                } else if (getType() == RcpConst.RCP_MSG_NOTI) {
                    checkValidPayloadFormat5(); // format0
                }
            } else if (getCode() == RcpConst.RCP_CMD_STOP_AUTO_READ) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_READ_C_DT) {
                ; // no check
            } else if (getCode() == RcpConst.RCP_CMD_GET_HOPPING_TBL) {
                checkValidPayloadFormat4(); // format4
            } else if (getCode() == RcpConst.RCP_CMD_SET_HOPPING_TBL) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_GET_MODULATION) {
                checkValidPayloadFormat0(0x04); // format0
            } else if (getCode() == RcpConst.RCP_CMD_SET_MODULATION) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_GET_ANTICOL_MODE) {
                checkValidPayloadFormat0(0x01); // format0
            } else if (getCode() == RcpConst.RCP_CMD_SET_ANTICOL_MODE) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_STRT_AUTO_READ_EX) {
                if (getType() == RcpConst.RCP_MSG_RSP) {
                    checkValidPayloadFormat1(); // format1
                } else if (getType() == RcpConst.RCP_MSG_NOTI) {
                    checkValidPayloadFormat5(); // format0
                }
            } else if (getCode() == RcpConst.RCP_CMD_READ_C_UII_TID) {
                if (getType() == RcpConst.RCP_MSG_RSP) {
                    checkValidPayloadFormat1(); // format1
                } else if (getType() == RcpConst.RCP_MSG_NOTI) {
                    checkValidPayloadFormat5(); // format0
                }
            } else if (getCode() == RcpConst.RCP_CMD_STOP_AUTO_READ_EX) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_WRITE_C_DT) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_BLOCKWRITE_C_DT) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_BLOCKERASE_C_DT) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_KILL_RECOM_C) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_LOCK_C) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_CMD_GET_TEMPERATURE) {
                checkValidPayloadFormat0(0x01); // format0
            } else if (getCode() == RcpConst.RCP_CMD_GET_RSSI) {
                checkValidPayloadFormat0(0x02); // format0
            } else if (getCode() == RcpConst.RCP_UPDATE_REGISTRY) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_ERASE_REGISTRY) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.RCP_GET_REGISTRY_ITEM) {
                ; // no check
            } else if (getCode() == RcpConst.GET_POWER_TABLE) {
                ; // no check
            } else if (getCode() == RcpConst.SET_POWER_TABLE) {
                checkValidPayloadFormat1(); // format1
            } else if (getCode() == RcpConst.COMMAND_FAIL) {
                checkValidPayloadFormat0(0x01); // format0
            }
        } catch (RcpException exception2) {
            throw exception2;
        }
        return;
    }

    private boolean isResponseFailure() {
        if (getCode() == RcpConst.COMMAND_FAIL
                && getType() == RcpConst.RCP_MSG_RSP) {
            return true;
        }
        return false;
    }

    private boolean isNotificationFailure() {
        if (getCode() == RcpConst.COMMAND_FAIL
                && getType() == RcpConst.RCP_MSG_NOTI) {
            return true;
        }
        return false;
    }

    /**
     * This checks whether the response is expected or not.
     *
     * @param code
     * @throws RcpException
     */
    public void checkExpectedResponse(byte code) throws RcpException {
        RcpException exception;

        if (isResponseFailure() == true) {
            switch (this.packet[PAYLOAD_INDEX]) {
                case RcpConst.READ_MEM_FAILURE:
                    exception = new RcpException("fail to read tag memory",
                            RcpConst.READ_MEM_FAILURE);
                    break;
                case RcpConst.WRITE_DATA_FAILURE:
                    exception = new RcpException("fail to write data",
                            RcpConst.WRITE_DATA_FAILURE);
                    break;
                case RcpConst.BUSY_READ_MULTIPLE:
                    exception = new RcpException(
                            "read type C tag ID multiple in operation",
                            RcpConst.BUSY_READ_MULTIPLE);
                    break;
                case RcpConst.NOT_IN_READ_MULTIPLE:
                    exception = new RcpException(
                            "not in mode - read type C tag ID multiple",
                            RcpConst.NOT_IN_READ_MULTIPLE);
                    break;
                case RcpConst.INVALID_COMMAND_PARAM:
                    exception = new RcpException("invalid command parameters",
                            RcpConst.INVALID_COMMAND_PARAM);
                    break;
                case RcpConst.KILL_TAG_FAILURE:
                    exception = new RcpException("fail to kill a tag",
                            RcpConst.KILL_TAG_FAILURE);
                    break;
                case RcpConst.LOCK_TAG_FAILURE:
                    exception = new RcpException("fail to lock a tag",
                            RcpConst.LOCK_TAG_FAILURE);
                    break;
                case RcpConst.READ_TAG_FAILURE:
                    exception = new RcpException("fail to read a tag",
                            RcpConst.READ_TAG_FAILURE);
                    break;
                case RcpConst.NOT_SUPPORTED_COMMAND:
                    exception = new RcpException("not supported command",
                            RcpConst.NOT_SUPPORTED_COMMAND);
                    break;
                case RcpConst.CRC_ERROR:
                    exception = new RcpException("CRC error", RcpConst.CRC_ERROR);
                    break;
                default:
                    exception = new RcpException("unknown failure",
                            RcpConst.UNKNOWN_FAILURE);
                    break;
            }

            throw exception;
        }

        if (getType() != RcpConst.RCP_MSG_RSP) {
            exception = new RcpException("not response", RcpConst.NOT_RESPONSE);
            throw exception;
        }

        if (getCode() != code) {
            exception = new RcpException("unexpected response",
                    RcpConst.UNEXPECTED_RESPONSE);
            throw exception;
        }

        return;
    }

    /**
     * This checks whether the notification is expected or not.
     *
     * @param code
     * @throws RcpException
     */
    public void checkExpectedNotification(byte code) throws RcpException {
        RcpException exception;

        if (isNotificationFailure() == true) {
            switch (this.packet[PAYLOAD_INDEX]) {
                case RcpConst.READ_MEM_FAILURE:
                    exception = new RcpException("fail to read tag memory",
                            RcpConst.READ_MEM_FAILURE);
                    break;
                case RcpConst.WRITE_DATA_FAILURE:
                    exception = new RcpException("fail to write data",
                            RcpConst.WRITE_DATA_FAILURE);
                    break;
                case RcpConst.BUSY_READ_MULTIPLE:
                    exception = new RcpException(
                            "read type C tag ID multiple in operation",
                            RcpConst.BUSY_READ_MULTIPLE);
                    break;
                case RcpConst.NOT_IN_READ_MULTIPLE:
                    exception = new RcpException(
                            "not in mode - read type C tag ID multiple",
                            RcpConst.NOT_IN_READ_MULTIPLE);
                    break;
                case RcpConst.INVALID_COMMAND_PARAM:
                    exception = new RcpException("invalid command parameters",
                            RcpConst.INVALID_COMMAND_PARAM);
                    break;
                case RcpConst.KILL_TAG_FAILURE:
                    exception = new RcpException("fail to kill a tag",
                            RcpConst.KILL_TAG_FAILURE);
                    break;
                case RcpConst.LOCK_TAG_FAILURE:
                    exception = new RcpException("fail to lock a tag",
                            RcpConst.LOCK_TAG_FAILURE);
                    break;
                case RcpConst.READ_TAG_FAILURE:
                    exception = new RcpException("fail to read a tag",
                            RcpConst.READ_TAG_FAILURE);
                    break;
                case RcpConst.NOT_SUPPORTED_COMMAND:
                    exception = new RcpException("not supported command",
                            RcpConst.NOT_SUPPORTED_COMMAND);
                    break;
                case RcpConst.CRC_ERROR:
                    exception = new RcpException("CRC error", RcpConst.CRC_ERROR);
                    break;
                default:
                    exception = new RcpException("unknown failure",
                            RcpConst.UNKNOWN_FAILURE);
                    break;
            }

            throw exception;
        }

        if (getType() != RcpConst.RCP_MSG_NOTI) {
            exception = new RcpException("not notificaiton",
                    RcpConst.NOT_NOTIFICATION);
            throw exception;
        }

        if (getCode() != RcpConst.RCP_CMD_READ_C_UII) {
            if (getCode() == code) {
                exception = new RcpException("multiple read complete",
                        RcpConst.AUTOREAD_COMPLETE);
            } else {
                exception = new RcpException("unexpected notification",
                        RcpConst.UNEXPECTED_NOTIFICATION);
                throw exception;
            }
            throw exception;
        }

        return;
    }
}
